/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */
/* $Id: sighold02.c,v 1.6 2005/08/31 20:27:16 robbiew Exp $ */
/*****************************************************************************
 * OS Test - Silicon Graphics, Inc.  Eagan, Minnesota
 * 
 * TEST IDENTIFIER : sighold02 Holding all signals.
 * 
 * PARENT DOCUMENT : sghtds01  sighold system call (CRAY X-MP and CRAY-1 only)
 * 
 * AUTHOR          : Bob Clark
 * 
 * CO-PILOT        : Barrie Kletscher
 * 
 * DATE STARTED    : 9/26/86
 * 
 * TEST ITEMS
 * 
 * 	1. sighold action to turn off the receipt of all signals was done
 *	   without error.
 * 	2. After signals were held, and sent, no signals were trapped.
 * 
 * SPECIAL PROCEDURAL REQUIRMENTS
 * 
 * 	The program must be linked with tst_res.o and parse_opts.o.
 * 
 * DETAILED DESCRIPTION
 * 
 * 	set up pipe for parent/child communications
 * 	fork off a child process
 * 
 * 	PARENT:
 * 		set up for unexpected signals
 * 		wait for child to send ready message over pipe
 *		issue a result for sighold
 * 		send all catchable signals to child
 *	        write to pipe, tell child all signals sent
 *		read pipe to get result from child
 * 		wait for child to terminate
 * 
 * 	CHILD:
 * 		set up to catch all signals
 * 		hold signals with sighold()
 * 		send parent sighold results via pipe
 * 		wait for signals to arrive while reading pipe
 *		write to pipe telling parent which signals were received if any.
 * 
 ***************************************************************************/

#include <errno.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "test.h"
#include "usctest.h"

/* Needed for NPTL */
#define SIGCANCEL 32
#define SIGTIMER 33

#ifdef _CRAYT3E
#define CRAYT3E 1
#else
#define CRAYT3E 0
#endif

#ifdef sgi
#define SGI 1
#else
#define SGI 0
#endif

#ifdef __linux__
/* glibc2.2 definition needs -D_XOPEN_SOURCE, which breaks other things. */
extern int sighold (int __sig);
#endif

/* ensure NUMSIGS is defined */
#ifndef NUMSIGS
#define NUMSIGS NSIG
#endif

#define CHILD_EXIT(VAL) ((VAL >> 8) & 0377) /* exit value of child process */

#define MAXMESG 150		/* the size of the message string */

#define TIMEOUT 2		/* time used in the alarm calls as backup */


char *TCID="sighold02";         /* Test program identifier.    */
int TST_TOTAL=2;                /* Total number of test cases. */
extern int Tst_count;           /* Test Case counter for tst_* routines */

char mesg[MAXMESG];		/* message buffer for tst_res */
char signals_received[MAXMESG];
int pid;			/* process id of child */
int Fds1[2];			/* file descriptors for pipe - child 2 parent */
int Fds2[2];			/* file descriptors for pipe - parent 2 child */

#define  PARENTSREADFD    Fds1[0]
#define  CHILDSWRITEFD    Fds1[1]

#define  CHILDSREADFD     Fds2[0]
#define  PARENTSWRITEFD   Fds2[1]


struct pipe_packet {
    int result;
    char mesg[MAXMESG];
    struct tblock rtimes;
} p_p;

void do_child();
void setup();
void cleanup();
static void getout();
static void timeout();
static int  read_pipe();
static int  write_pipe();
static int  setup_sigs();
static void handle_sigs();
static int  set_timeout();
static void clear_timeout();

int Timeout = 0;

/***********************************************************************
 * MAIN
 ***********************************************************************/
int
main(int ac, char **av)
{
    int term_stat;	/* child return status */
    int sig;		/* current signal */
    int lc;             /* loop counter */
    char *msg;          /* message returned from parse_opts */

    /***************************************************************
     * parse standard options, and exit if there is an error
     ***************************************************************/
    if ( (msg=parse_opts(ac, av, (option_t *) NULL, NULL)) != (char *) NULL ) {
        tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);
        tst_exit();
    }

#ifdef UCLINUX
    maybe_run_child(&do_child, "dd", &CHILDSWRITEFD, &CHILDSREADFD);
#endif

    /***************************************************************
     * perform global setup for test
     ***************************************************************/
    setup();

    /***************************************************************
     * check looping state if -c option given
     ***************************************************************/
    for (lc=0; TEST_LOOPING(lc); lc++) {

        /* reset Tst_count in case we are looping. */
        Tst_count=0;

	signals_received[0] = '\0';

	/*
	 * fork off a child process
	 */
	if ((pid = FORK_OR_VFORK()) < 0) {
	    (void) sprintf(mesg, "fork() failed. error:%d %s.",
		errno, strerror(errno));
	    tst_brkm(TBROK, cleanup, mesg);

	} else if (pid > 0) {

	    /* PARENT PROCESS - first set up for unexpected signals */
	    tst_sig(FORK, DEF_HANDLER, getout);


	    /* wait for "ready" message from child */
	    if ( read_pipe(PARENTSREADFD, 0) != 0 ) {
		/* read_pipe() failed. */
		tst_brkm(TBROK, getout, p_p.mesg);
	    }

            if (STD_TIMING_ON) {
                /*
                 * Insure a running total for multiple loop iterations
                 */
                 tblock.tb_total += p_p.rtimes.tb_total;
                 if ( p_p.rtimes.tb_min < tblock.tb_min )
                     tblock.tb_min=p_p.rtimes.tb_min;
                 if ( p_p.rtimes.tb_max > tblock.tb_max )
                     tblock.tb_max=p_p.rtimes.tb_max;
                 tblock.tb_count += p_p.rtimes.tb_count;
            }

	    /* check for ready message */
	    if ( p_p.result != TPASS ) {
		/* child setup did not go well */
		tst_brkm(p_p.result, getout, p_p.mesg);
	    }
	    else if ( STD_FUNCTIONAL_TEST ) {
		tst_resm(p_p.result, p_p.mesg);
	    }
	    else {	/* no pass results being issued */
		Tst_count++;
	    }

	    /*
	     * send signals to child and see if it holds them 
	     */

	    for (sig = 1; sig < NUMSIGS; sig++) {
                if ((sig == 41) && !CRAYT3E && !SGI) {
                        sig = 42;  /* skip over SIGPEFAILURE for non-CRAYT3E systems */
                }
		if ((sig != SIGCLD) && (sig != SIGKILL) &&
		    (sig != SIGALRM) && (sig != SIGSTOP) 
		    && (sig != SIGCANCEL)
		    && (sig != SIGTIMER)
#ifdef SIGRECOVERY
		    && (sig != SIGRECOVERY) 
#endif
#ifdef SIGRESTART
		    && (sig != SIGRESTART) 
#endif
#ifdef SIGNOBDM
		    && (sig != SIGNOBDM)
#endif
#ifdef SIGPTINTR
		    && (sig != SIGPTINTR)
#endif
		) {
		    if (kill(pid, sig) < 0) {
		        (void) sprintf(mesg,
			    "kill() failed. sig:%d error:%d %s.",
			    sig, errno, strerror(errno));
		        tst_brkm(TBROK, NULL, mesg);
		        getout();
		    }
		}
	    }

	    /*
	     * Tell child that all signals were sent.
	     */
	    p_p.result = TPASS;
	    strcpy(p_p.mesg, "All signals were sent");
	    if ( write_pipe(PARENTSWRITEFD) < 0 ) {
		tst_brkm(TBROK, getout, mesg);
	    }

	    /*
	     * Get childs reply about received signals.
	     */

	    if ( read_pipe(PARENTSREADFD, 0) < 0 ) {
		tst_brkm(TBROK, getout, mesg);
	    }

	    if ( STD_FUNCTIONAL_TEST )
	        tst_resm(p_p.result, p_p.mesg);

	    else if ( p_p.result != TPASS ) 
	        tst_resm(p_p.result, p_p.mesg);

            else
		Tst_count++;
	   

	    /*
	     * wait for child 
	     */
	    if (wait(&term_stat) < 0) {
		(void) sprintf(mesg, "wait() failed. error:%d %s.",
			errno, strerror(errno));
		tst_brkm(TBROK, NULL, mesg);
		getout();
	    }

	} else {

	    /*
	     * CHILD PROCESS - set up to catch signals.
	     */

#ifdef UCLINUX
	    if (self_exec(av[0], "dd", CHILDSWRITEFD, CHILDSREADFD) < 0) {
		(void) sprintf(mesg, "self_exec failed");
		tst_brkm(TBROK, cleanup, mesg);
	    }
#else
	    do_child();
#endif
	}
    }
    cleanup();

    return 0;
}

/*****************************************************************************
 *  do_child()
 ****************************************************************************/

void
do_child()
{
    int rv;		/* function return value */
    int sig;		/* current signal */
    int cnt;

    p_p.result=TPASS;
	
    /* set up signal handlers for the signals */
    if (setup_sigs() < 0) {
	p_p.result=TBROK;
	strcpy(p_p.mesg, mesg);

    } else {
	/* all set up to catch signals, now hold them */

	for (cnt=0, sig = 1; sig < NUMSIGS; sig++) {
	    if ((sig == 41) && !CRAYT3E && !SGI) {
		sig = 42;  /* skip over SIGPEFAILURE for non-CRAYT3E systems */
	    }
	    if ((sig != SIGCLD) && (sig != SIGKILL) &&
		(sig != SIGALRM) && (sig != SIGSTOP)
#ifdef SIGNOBDM
		&& (sig != SIGNOBDM )
#endif
		&& (sig != SIGCANCEL) && (sig != SIGTIMER)
		) {

		cnt++;
		TEST( sighold(sig) );
		rv = TEST_RETURN;
		if (rv != 0) {
		    /* THEY say sighold ALWAYS returns 0 */
		    p_p.result=TFAIL;
		    (void) sprintf(p_p.mesg,
				   "sighold(%d) failed, rv:%d, errno:%d",
				   sig, rv, errno);
		    break;
		}
	    }
	}
	if ( STD_TIMING_ON ) {
	    p_p.rtimes = tblock;
	}
	if ( p_p.result == TPASS ) {
	    sprintf(p_p.mesg,
		    "Sighold called without error for %d of %d signals",
		    cnt, NUMSIGS-1);
	}
    }

    /*
     * write to parent (if not READY, parent will BROK) and
     * wait for parent to send signals.  The timeout clock is set so
     * that we will not wait forever - if sighold() did its job, we
     * will not receive the signals.  If sighold() blew it we will
     * catch a signal and the interrupt handler will exit(1).
     */
#if debug
    printf("child: %d writing to parent fd:%d\n", getpid(), CHILDSWRITEFD);
#endif
    if (write_pipe(CHILDSWRITEFD) < 0 || p_p.result != TPASS ) {
	exit(2);
    }

    /*
     * Read pipe from parent, that will tell us that all signals were sent
     */
    if ( read_pipe(CHILDSREADFD, 0) != 0 ) {
	p_p.result = TBROK;
	strcpy(p_p.mesg, mesg);
    }
    else if ( signals_received[0] == '\0' ) {
	p_p.result = TPASS;
	strcpy(p_p.mesg, "No signals trapped after being sent by parent");
    }
    else {
	p_p.result = TFAIL;
	sprintf(p_p.mesg, "signals received: %s", signals_received);
    }
		
    if (write_pipe(CHILDSWRITEFD) < 0 ) {
	exit(2);
    }

    /* exit back to parent */
    if ( p_p.result == TPASS )
	exit(0);
    else
	exit(1);
}

/*****************************************************************************
 *  read_pipe() : read data from pipe and return in buf.  If an error occurs
 *      put message in mesg and return NULL.  Note: this routine sets a 
 *      timeout signal in case the pipe is blocked.
 ****************************************************************************/

int 
read_pipe(fd)
int fd;
{
    int ret = -1;

#ifdef debug
    printf("read_pipe: %d entering, fd = %d...\n", getpid(), fd);
#endif 

    /* set timeout alarm in case the pipe is blocked */
    if (set_timeout() < 0) {
	/* an error occured, message in mesg */
	return -1;
    }

    ret=read(fd, (char *)&p_p, sizeof(struct pipe_packet));

#ifdef debug
    printf("read_pipe: %d read() completed ret=%d\n", getpid(), ret);
#endif 

    clear_timeout();

    if ( Timeout ) {
	(void) sprintf(p_p.mesg, 
		"read() pipe failed -timed out after %d seconds.", TIMEOUT);
	return -1;
    }
#ifdef debug
    printf("read_pipe: received %s.\n", p_p.mesg);
#endif 

    return 0;
}


/*****************************************************************************
 *  write_pipe(msg) : write p_p to pipe.  If it fails, put message in 
 *         mesg and return -1, else return 0.
 ****************************************************************************/

static int
write_pipe(fd)
int fd;
{
#ifdef debug
    printf("write_pipe: sending result:%d, mesg:%s.\n", p_p.result, p_p.mesg);
#endif
    if (write(fd, (char *)&p_p, sizeof(struct pipe_packet)) < 0) {
	(void) sprintf(mesg, "write() pipe failed. error:%d %s.",
		errno, strerror(errno));
	return -1;
    }
#ifdef debug
    printf("write_pipe: %d complete successfully, fd:%d.\n", getpid(), fd);
#endif
    return 0;
}

/*****************************************************************************
 *  set_timeout() : set alarm to signal process after the period of time
 *       indicated by TIMEOUT.  If the signal occurs, the routine timeout()
 *       will be executed.  If all goes ok, return 0, else load message
 *       into mesg and return -1.
 ****************************************************************************/

static int
set_timeout()
{
    if (signal(SIGALRM, timeout) == SIG_ERR) {
	(void) sprintf(mesg, "signal() failed for signal %d. error:%d %s.",
		SIGALRM, errno, strerror(errno));
	return(-1);
    }
#if debug
printf("set_timeout()...\n");
#endif

    Timeout=0;
    (void) alarm(TIMEOUT);
    return(0);
}

/*****************************************************************************
 *  clear_timeout() : turn off the alarm so that SIGALRM will not get sent.
 ****************************************************************************/

static void
clear_timeout()
{
    (void) alarm(0);
    Timeout=0;
}

/*****************************************************************************
 *  timeout() : this routine is executed when the SIGALRM signal is 
 *      caught.  It does nothing but return - if executed during a read()
 *      system call, a -1 will be returned by the read().
 ****************************************************************************/

static void
timeout() 
{
#ifdef debug
    printf("timeout: sigalrm caught.\n");
#endif
    Timeout=1;
	
}

/*****************************************************************************
 *  setup_sigs() : set child up to catch all signals.  If there is
 *       trouble, write message in mesg and return -1, else return 0.
 ****************************************************************************/

static int
setup_sigs()
{
    int sig;

    /* set up signal handler routine */
    for (sig = 1; sig < NUMSIGS; sig++) {
	if ((sig != SIGCLD) && (sig != SIGKILL) &&
	(sig != SIGALRM) && (sig != SIGSTOP) 
#ifdef SIGRESTART
	&& (sig != SIGRESTART)
#endif
#ifdef SIGRECOVERY
	&& (sig != SIGRECOVERY)
#endif
#ifdef SIGSWAP
	&& (sig != SIGSWAP)
#endif
	&& (sig != SIGCANCEL) && (sig != SIGTIMER)
	) {

	    if (signal(sig, handle_sigs) == SIG_ERR) {
		/* set up mesg to send back to parent */
		(void) sprintf(mesg,
		    "signal() failed for signal %d. error:%d %s.",
		    sig, errno, strerror(errno));
		return(-1);
	    }
	}
    }
    return(0);
}

/*****************************************************************************
 *  handle_sigs() : interrupt handler for all signals.  This will be run
 *      if the child process catches a signal (resulting in
 *      a test item FAIL if tst_res has not yet been called).  The parent
 *      detects this situation by a child exit value of 1.
 ****************************************************************************/

static void
handle_sigs(sig)
int sig;	/* the signal causing the execution of this handler */
{
    char string[10];

#ifdef debug
	printf("child: handle_sigs: caught signal %d.\n", sig);
#endif

    sprintf(string, " %d", sig);
    strcat(signals_received, string);

    return;
}

	
/*****************************************************************************
 *  getout() : attempt to kill child process and call cleanup().
 ****************************************************************************/

static void
getout()
{
    if (kill(pid, SIGKILL) < 0) {
	(void) sprintf(mesg,
	    "kill() failed. Child may not have been killed. error:%d %s.",
	    errno, strerror(errno));
	tst_resm(TWARN, mesg);
    }
    cleanup();
}


/***************************************************************
 * setup() - performs all ONE TIME setup for this test.
 ***************************************************************/
void
setup()
{
    /* capture signals */
    tst_sig(FORK, DEF_HANDLER, cleanup);

    /* set up pipe for child sending to parent communications */
    if (pipe(Fds1) < 0) {
	(void) sprintf(mesg, "pipe() failed. error:%d %s.",
  	    errno, strerror(errno));
	tst_brkm(TBROK, cleanup, mesg);
    }
    
    /* set up pipe for parent sending to child communications */
    if (pipe(Fds2) < 0) {
	(void) sprintf(mesg, "pipe() failed. error:%d %s.",
  	    errno, strerror(errno));
	tst_brkm(TBROK, cleanup, mesg);
    }

#if debug
printf("child 2 parent Fds1[0] = %d, Fds1[1] = %d\n", Fds1[0], Fds1[1]);
printf("parent 2 child Fds2[0] = %d, Fds2[1] = %d\n", Fds2[0], Fds2[1]);
#endif

    /* Pause if that option was specified */
    TEST_PAUSE;

}       /* End setup() */


/***************************************************************
 * cleanup() - performs all ONE TIME cleanup for this test at
 *              completion or premature exit.
 ***************************************************************/
void
cleanup()
{
    /*
     * print timing stats if that option was specified.
     * print errno log if that option was specified.
     */
    TEST_CLEANUP;

    /* exit with return code appropriate for results */
    tst_exit();

}       /* End cleanup() */

